﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.EnterpriseLibrary.Caching;
using Microsoft.Practices.EnterpriseLibrary.Caching.Expirations;
using MRS.FrontEnd.BO;

namespace Microsoft.Reputation.Services
{
    public class PNPCacheProvider : IDisposable, ICacheProvider
    {
        private ICacheManager m_lookupCache;
        private int m_defaultExpirationMins;
        private int m_noMatchCacheTime;

        public PNPCacheProvider() : this(CacheFactory.GetCacheManager()) { }

        public PNPCacheProvider(ICacheManager cacheManager)
        {
            this.m_lookupCache = cacheManager;

            this.m_defaultExpirationMins = DeliveryServiceConfig.CacheExpirationMinutes;

            // Safe to cast as the value from DeliveryServiceConfig is between 0 and int.MaxValue
            m_noMatchCacheTime = (int)DeliveryServiceConfig.NoMatchExpirationMinutes;
        }

        #region IDisposable Members

        void IDisposable.Dispose()
        {
            m_lookupCache.Flush();
            m_lookupCache = null;
        }

        #endregion

        #region ICacheProvider Members
        public bool GetMatchedEntry(ObjectIdentifier objectIdentefier, out UnpackedObject matchEntry)
        {
            matchEntry = null;
            byte[] packedData = m_lookupCache.GetData(GetCacheKey(objectIdentefier)) as byte[];

            if (packedData != null)
            {
                IReputationBinaryCodec codec = UnpackedObject.ReputationBinaryCodecFactory(packedData);

                matchEntry = codec.UnpackObjectBinary(packedData);
            }
            return (matchEntry != null);
        }

        // This is used for Override lookups
        public bool GetMatchedEntries(IEnumerable<ObjectIdentifier> objectIdentefiers, out List<UnpackedObject> matches)
        {
            matches = new List<UnpackedObject>();

            foreach (ObjectIdentifier objectId in objectIdentefiers)
            {
                UnpackedObject unpackedObject;

                if (this.GetMatchedEntry(objectId, out unpackedObject))
                {
                    matches.Add(unpackedObject);
                }
            }

            return (matches.Count == objectIdentefiers.Count());
        }

        public void CacheMatchedEntry(UnpackedObject match)
        {
            byte[] packFromCache = null;
            IReputationBinaryCodec codec = UnpackedObject.ReputationBinaryCodecFactory(match.Type);

            string cacheKey = GetCacheKey(match.Type, match.Sha256);

            // Check the versions of the objects ot make sure that we are not trying
            // to insert an older object in to the cache
            packFromCache = m_lookupCache.GetData(cacheKey) as byte[];
            if (packFromCache != null)
            {
                UnpackedObject unpackedFromCache = codec.UnpackObjectBinary(packFromCache);
                if (unpackedFromCache.Version >= match.Version)
                {
                    // The object we got from the database is older so don't bother to pack it or
                    // put it in the cache
                    return;
                }
            }

            TimeSpan experationSpan = DetermineCacheExpirationTime(match);
            m_lookupCache.Add(cacheKey, codec.PackObjectBinary(match), CacheItemPriority.Normal, null,
                new AbsoluteTime(experationSpan));
        }

        public void CacheMatchedEntries(IEnumerable<UnpackedObject> matches)
        {
            foreach (UnpackedObject objectToCache in matches)
            {
                this.CacheMatchedEntry(objectToCache);
            }
        }

        #endregion

        internal static string GetCacheKey(ObjectType objectType, byte[] sha256)
        {
            return GetCacheKey(new ObjectIdentifier { Type = objectType, Sha256 = sha256 });
        }

        internal static string GetCacheKey(ObjectIdentifier objectId)
        {
            if (objectId == null || objectId.Sha256 == null || objectId.Sha256.Length != 32)
            {
                throw new ArgumentNullException("ObjectIdentifier.SHA256");
            }

            return Convert.ToBase64String(objectId.Sha256);
        }

        private TimeSpan DetermineCacheExpirationTime(UnpackedObject match)
        {
            TimeSpan experationSpan;
            if (match.Version == Int32.MinValue)
            {
                // This is a placeholder object.  It is meant to avoid
                // excessive querying of the database
                experationSpan = new TimeSpan(0, m_noMatchCacheTime, 0);
            }
            else if (match.ServerExpirationInMinutes != 0)
            {
                experationSpan = new TimeSpan(0, match.ServerExpirationInMinutes, 0);
            }
            else
            {
                experationSpan = new TimeSpan(0, this.m_defaultExpirationMins, 0);
            }
            return experationSpan;
        }

    }
}
